package io.zbus.data.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import io.zbus.data.impl.meta.MetaData;
import io.zbus.data.impl.meta.Table;

public class JsonConditionParser {
	private MetaData meta;
	private Map<String, String> aliasMapping = new HashMap<>();
	private String defaultTable; 
	
	public JsonConditionParser() { 
	}
	
	public JsonConditionParser(MetaData meta, String sql) {
		this.meta = meta; 
		parseTableAlias(sql);
	}   
	
	public JsonConditionParser(MetaData meta) {
		this.meta = meta;  
	}   
	
	public void setTable(String aliasTable, String table) {
		aliasMapping.put(aliasTable, table);
		defaultTable = table;
	}
	
	protected void parseTableAlias(String sql) {
		
	}
	
	private String column(String key) { 
		String[] bb = key.split("[.]");
		String t = null, c = null;
		if(bb.length>1) {
			t = bb[0].trim();
			c = bb[1].trim();
		}
		else c = bb[0].trim(); 
		
		if(meta != null && !aliasMapping.isEmpty() && defaultTable !=null) { 
			if(t == null) t = defaultTable;
			String tableName = aliasMapping.get(defaultTable);
			if(tableName == null) return null;
			Table table = meta.tables.get(tableName);
			if(table == null) return null;
			if( !table.columns.containsKey(c) ) return null;
		}
		
		return key;
	} 
	
	public String parse(Object where, List<Object> paramList) {
		if(where == null) return null;
		Expression exp = parseExpression(where, null);
		return convert(exp, paramList);
	} 
	
	@SuppressWarnings("unchecked")
	String convert(Expression data, List<Object> paramList) {
		if(data instanceof SimpleExpression) {
			SimpleExpression exp = (SimpleExpression)data; 
			if(exp.op == null) return null; //ignore invalid op
			if(exp.column == null) return null;
			String e = String.format("%s %s ", exp.column, exp.op);
			if("in".equalsIgnoreCase(exp.op)) {//special in 
				List<String> qmarkList = new ArrayList<>();
				List<Object> valueList;
				if(exp.value instanceof List) {
					valueList = (List<Object>)exp.value;
				} else {
					valueList = new ArrayList<>();
					valueList.add(exp.value);
				}
				for(int i=0; i<valueList.size(); i++) {
					qmarkList.add("?");
				}
				e += "(" + String.join(",", qmarkList) + ")";
				paramList.addAll(valueList);
			}  else {
				e += "?";
				paramList.add(exp.value);
			}
			return e; 
		}
		CompositeExpression exp = (CompositeExpression)data; 
		List<String> subExpLilst = new ArrayList<>(); 
		for(Expression subData : exp.chidlren) {
			String subExp = convert(subData, paramList);
			if(subExp != null) {
				subExpLilst.add(subExp);
			}
		}
		String res = String.join(" " + exp.joinType + " ", subExpLilst);
		if(subExpLilst.size() > 1) {
			res = "(" + res + ")";
		}
		return res;
	}
	
	
	Expression parseExpression(Object data, String key) {
		if(!(data instanceof JSONArray || data instanceof JSONObject)) {
			if(key == null) {
				throw new IllegalArgumentException("key missing for Non-JSON object");
			}
			SimpleExpression exp = new SimpleExpression();
			exp.column = column(key);
			exp.op = "=";
			exp.value = data;
			return exp;
		}
		
		if(data instanceof JSONArray){
			JSONArray array = (JSONArray)data;
			CompositeExpression exp = new CompositeExpression(); 
			for(Object sub : array) {
				exp.chidlren.add(parseExpression(sub, null));
			}
			if(key == null || key.equalsIgnoreCase("and")) {
				exp.joinType = "and";
			} else if (key.equalsIgnoreCase("or")) {
				exp.joinType = "or";
			} else {
				throw new IllegalArgumentException("invalid json " + data);
			}
			return exp;
		}
		
		if(data instanceof JSONObject){
			JSONObject json = (JSONObject)data;
			if(json.isEmpty()) {
				throw new IllegalArgumentException("invalid json " + json);
			}
			if(json.size() == 1) {
				if(key == null) {
					for(Entry<String, Object> e : json.entrySet()) {
						return parseExpression(e.getValue(), e.getKey());
					}
				}
				
				for(Entry<String, Object> e : json.entrySet()) {
					if(!(e.getValue() instanceof JSONObject)) {
						SimpleExpression exp = new SimpleExpression();
						exp.column = column(key);
						exp.op = resolveOp(e.getKey());
						exp.value = resolveValue(e.getKey(), e.getValue());
						return exp;
					} 
				}  
			}
			//multiple key-value
			if(key != null &&  !key.equalsIgnoreCase("and") && !key.equalsIgnoreCase("or")) {
				throw new IllegalArgumentException("invalid json " + json);
			}
			CompositeExpression exp = new CompositeExpression(); 
			for(Entry<String, Object> e : json.entrySet()) { 
				exp.chidlren.add(parseExpression(e.getValue(), e.getKey()));
			}
			exp.joinType = "and";
			if("or".equalsIgnoreCase(key)){
				exp.joinType = "or";
			} else if("not".equalsIgnoreCase(key)){
				exp.joinType = "not";
			} 
			return exp; 
		}
		
		return null;
	}  
	
	static String resolveOp(String op) {
		if(op == null) return op;
		op = op.toLowerCase();
		if(sqlOpAliasMapping.containsKey(op)) {
			op = sqlOpAliasMapping.get(op);
		}
		if(!sqlOperators.contains(op)) return null;
		return op; 
	}
	
	static Object resolveValue(String op, Object value) {
		if(op == null) return value;
		op = op.toLowerCase();
		if("c".equals(op) || "contains".equals(op) || "like".equals(op)) {
			return String.format("%%%s%%", value);
		}
		if("sw".equals(op) || "startswith".equals(op)) {
			return String.format("%s%%", value);
		}
		if("ew".equals(op) || "endsswith".equals(op)) {
			return String.format("%%%s", value);
		}
		return value;
	}
	
	static interface Expression {  }
	
	static class SimpleExpression implements Expression{
		public String column;
		public String op;
		public Object value;
	}
	
	static class CompositeExpression implements Expression{
		public String joinType; //and, or, not
		public List<Expression> chidlren = new ArrayList<>();
	}  
	
	private static final Map<String, String> sqlOpAliasMapping = new HashMap<>();
	private static final Set<String> sqlOperators = new HashSet<>();
	static {
		sqlOpAliasMapping.put("eq", "=");
		sqlOpAliasMapping.put("gt", ">");
		sqlOpAliasMapping.put("gteq", ">=");
		sqlOpAliasMapping.put("lt", "<");
		sqlOpAliasMapping.put("lteq", "<=");
		sqlOpAliasMapping.put("c", "like");
		sqlOpAliasMapping.put("contains", "like");
		sqlOpAliasMapping.put("sw", "like");
		sqlOpAliasMapping.put("startswith", "like");
		sqlOpAliasMapping.put("ew", "like");
		sqlOpAliasMapping.put("endswith", "like"); 
		
		sqlOperators.add("=");
		sqlOperators.add(">");
		sqlOperators.add(">=");
		sqlOperators.add("<");
		sqlOperators.add("<=");
		sqlOperators.add("<>");
		sqlOperators.add("all");
		sqlOperators.add("and");
		sqlOperators.add("any");
		sqlOperators.add("between");
		sqlOperators.add("exists");
		sqlOperators.add("in");
		sqlOperators.add("like");
		sqlOperators.add("not");
		sqlOperators.add("or");
		sqlOperators.add("some");
	}
}
